package com.mjk.common.core.jdbc.transaction;

import com.mjk.common.base.error.BeeError;
import com.mjk.common.base.error.BizException;
import com.mjk.common.base.model.BaseEntity;
import com.mjk.common.base.response.BeeResponse;
import com.mjk.common.core.jdbc.annotation.ArcSharding;
import com.mjk.common.core.jdbc.annotation.DataBaseId;
import com.mjk.common.core.jdbc.datasource.DynamicDataSource;
import com.mjk.common.core.jdbc.transaction.annotation.BeeTransactional;
import com.mjk.common.tools.aop.BaseAop;
import com.mjk.common.tools.gson.GsonUtils;
import com.mjk.common.tools.logger.BizLogger;
import com.mjk.common.tools.springextend.BeeApplicationListener;
import com.mjk.common.tools.springextend.BeeSpringBean;
import com.mjk.common.util.*;
import org.apache.ibatis.session.SqlSession;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Transaction 切面，如果开启事务，用该切面来切换数据源
 * 1 通过适配方法名称 获取连接池
 * 2 自定义事务注解 指定连接池
 * @author mjk
 *
 */
@Component
@Aspect
@Order(value=1)
public class TransactionAop extends BaseAop implements BeeApplicationListener {
	
	@Value("${sharding.jdbc.datasource.defaultname:his}")
	private String WRITE_BASESOURCE_NAME;
	
	@Value("${sharding.jdbc.datasource.readename:readhis}")
	private String READ_BASESOURCE_NAME;
	
	@Pointcut("@annotation(com.mjk.common.core.jdbc.transaction.annotation.BeeTransactional)")
	public void pointcut() {
	}
	@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
	public void pointcut1() {
	}	
	/**
	 * bee切面
	 */
    @Pointcut("execution(public * com.mjk..*Mapper.*(..)) || execution(public * com.hd..*Mapper.*(..))")
	public void dataBaseId(){}

    /**
	 * 业务代码切面
	 */
    @Pointcut("execution(public * com.mjk..*Mapper.*(..)) || execution(public * com.hd..*Mapper.*(..))")
	public void auth(){}

    private final String[] readMethods = new String[]{"get","selectByExample"};
	
	private final String[] notReadMethos = new String[]{"getPbsz","getInOutDepotDh","getInventorySlipDH"
			,"getPayService"};
	
	 /**
     * 这个map用于存放sql语句所对应的数据源
     */
    private static final Map<String, String> cacheMap = new ConcurrentHashMap<>();
    
	/**
	 * mapper中对于databaseID的选着
     */
	@Around("dataBaseId()||auth()")
	public Object selectDateBaseId(ProceedingJoinPoint joinPoint)throws Throwable{
		
		Object target = joinPoint.getTarget();
		Object o = ReflectionUtils.getFieldValue(target, "h");
		if(ObjectUtils.isEmpty(ReflectionUtils.getField(o.getClass(), "mapperInterface"))){
			o =ReflectionUtils.getFieldValue(AopTargetUtils.getTarget(target), "h");
		}		
		Object mappeInterface =ReflectionUtils.getFieldValue(o, "mapperInterface");	
		Object className;
		try{
			className = mappeInterface.toString().replaceFirst("interface", "").trim();
		}catch(Exception e){
			className =ReflectionUtils.getFieldValue(mappeInterface, "name");
		}	
		String methodName = joinPoint.getSignature().getName();	
		String key = className+"."+methodName;		
       
		if(cacheMap.containsKey(key)){
			String oldDataBaseType = DynamicDataSource.getDatabaseType();
			String newDataBaseType = cacheMap.get(key);
			if(!newDataBaseType.equals(oldDataBaseType)){
				DynamicDataSource.setDatabaseType(newDataBaseType);
				Object result = joinPoint.proceed(joinPoint.getArgs());
				DynamicDataSource.setDatabaseType(oldDataBaseType);
				return result;
			}
		}else{
			boolean isRead = false;
			//如果当前线程中的数据库连接不是读库或者写库的，这里不能进行判定
			String nowType = DynamicDataSource.getDatabaseType();
			if(!ObjectUtils.isEmpty(nowType)&&(nowType.equals(READ_BASESOURCE_NAME)||nowType.equals(WRITE_BASESOURCE_NAME))){
				for(String mName : readMethods){
					if(methodName.contains(mName)){
						boolean isNotRead = false;
						for(String notRead  : notReadMethos){
							if(methodName.contains(notRead)){
								isNotRead = true;
								break;
							}
						}
						if(!isNotRead) {
                            isRead = true;
                        }
					}
				}
		        if(isRead&&!"SeqMapper".equals(joinPoint.getTarget().getClass().getSimpleName())){
		        	DynamicDataSource.setDatabaseType(READ_BASESOURCE_NAME);
		        }else{
		        	//BeeLogContentHolder.needLog.set(true);//如果涉及到写库的数据 需要保存数据
		        	DynamicDataSource.setDatabaseType(WRITE_BASESOURCE_NAME);
		        }
			}else{
				if(ObjectUtils.isEmpty(nowType)){
					DynamicDataSource.setDatabaseType(WRITE_BASESOURCE_NAME);
				}
			}
		}
		BizLogger.info("active datesoruce is "+DynamicDataSource.getDatabaseType());
		SqlSession session = BeeSpringBean.getBean(SqlSession.class);
		Object obj =  joinPoint.proceed(joinPoint.getArgs());
		//清除一级缓存
		Objects.requireNonNull(session).clearCache();
		return obj;
	}
		
	
	
	@SuppressWarnings("finally")
	@Around("pointcut()||pointcut1()")
	public Object doTransation(ProceedingJoinPoint joinPoint) {
		int isError = 1;
		String errorMsg = "";
		Object result = null;
		BaseAopModel model = getPointInfo(joinPoint);
		Object target = model.getTarget();		
		BeeTransactional beeTransaction = model.getMethod().getAnnotation(BeeTransactional.class);
		if(null != beeTransaction){
			DynamicDataSource.setDatabaseType(beeTransaction.dataBaseId());
		}else{
			Transactional transaction = model.getMethod().getAnnotation(Transactional.class);
			if(null != transaction){
				//获取默认值
				DynamicDataSource.setDatabaseType(WRITE_BASESOURCE_NAME);
			}
		}
		try{
			Object[] args = joinPoint.getArgs();					
			BizLogger.info("request target:["+target.getClass().getSimpleName()+
					"] method["+model.getMethod().getName()+"] params:"+ StrUtils.join(Arrays.asList(args), ","));
			//判断参数是否是baseentity 如果是需要根据arcsharding切换数据源
			if(args.length > 0 && args[0] instanceof BaseEntity) {
				ArcSharding sharding =  args[0].getClass().getAnnotation(ArcSharding.class);
				if(null!=sharding && !ObjectUtils.isEmpty(sharding.dataSource())) {
					DynamicDataSource.setDatabaseType(sharding.dataSource());
				}else {
					DynamicDataSource.setDatabaseType(WRITE_BASESOURCE_NAME);
				}
			}
			result = joinPoint.proceed(args);	
			//对于有Transactional 注解的方法，如果返回的不是 成功，则需要回滚 这里统一做抛出异常处理
			if(result instanceof BeeResponse rsp){
				Annotation[] as = model.getMethod().getAnnotations();
				boolean isNeedThrowsException = false;
				for(Annotation a : as){
                    if (a instanceof Transactional || a instanceof BeeTransactional) {
                        isNeedThrowsException = true;
                        break;
                    }
				}
				if(isNeedThrowsException){
					if(!rsp.getCode().equals(BeeError.BEE_SUC.getCode())){
						isError = -1;
						errorMsg = GsonUtils.bean2Json(result);
					}
				}
			}
		}catch(Exception e){//抛异常统一返回 未知错误
			isError = -1;
			BeeResponse response = new BeeResponse();
			response.setCode(BeeError.BEE_FAILD.getCode());
			response.setMsg(e.getMessage());
			errorMsg=GsonUtils.bean2Json(response);
		}finally{
			//如果执行过程抛出异常，则抛出统一异常，进行回滚操作
			if(isError == -1){
				throw new BizException(errorMsg);
			}else{
				return result;
			}
		}	
	}
	
	
	@Override
	public void doApplicationEvent(ContextRefreshedEvent event) {
		Map<String,Object> mappers = event.getApplicationContext().getBeansWithAnnotation(org.apache.ibatis.annotations.Mapper.class);
		for(Entry<String,Object> entry : mappers.entrySet()){
			Object o =ReflectionUtils.getFieldValue(entry.getValue(), "h");
			if(ObjectUtils.isEmpty(ReflectionUtils.getField(o.getClass(), "mapperInterface"))){
				Object target =AopTargetUtils.getTarget(entry.getValue());
				o =ReflectionUtils.getFieldValue(target, "h");				
			}
			Object mappeInterface =ReflectionUtils.getFieldValue(o, "mapperInterface");
			Object className;
			try{
				className = mappeInterface.toString().replaceFirst("interface", "").trim();
			}catch(Exception e){
				className =ReflectionUtils.getFieldValue(mappeInterface, "name");
			}
			Method[] methds = Objects.requireNonNull(ClassUtil.loadClass(className.toString())).getDeclaredMethods();
			for(Method m : methds){
				DataBaseId database = m.getAnnotation(DataBaseId.class);
				if(database!=null){
					cacheMap.put(className+"."+m.getName(), database.name());
				}
			}			
		}	
	}	
}
